﻿using System;
using System.Net.Mail;
using System.Web;
using System.Web.Security;
using Castle.Core.Logging;
using DotNetOpenAuth.AspNet;
using User.EF.Contracts;
using User.EF.Model;
using User.Global.Contracts.Membership;
using User.Global.Contracts.Message;
using User.Global.Resources;

namespace User.Web.Common.Membership
{
    public class OAuthMembership : IOAuthMembership<Member>
    {
        #region Member
        public AuthenticationResult AuthResult { get; set; }

        /// <summary>
        /// Allgemeine Messageverwaltung für die jeweiligen Meldungen die ausgegeben werden sollen.
        /// </summary>
        public IAddMessage Message { get; set; }

        /// <summary>
        /// Der aktuelle Logger
        /// </summary>
        public ILogger Logger { get; set; }

        private IWebQuerySummary _webQuery;
        #endregion

        #region Initialize
        public OAuthMembership(IAddMessage message, AuthenticationResult authResult, IWebQuerySummary webQuery)
        {
            if (message == null)
            {
                throw new NullReferenceException("Message is Null");
            }

            if (webQuery == null)
            {
                throw new NullReferenceException("WebQuery is Null");
            }

            if (authResult == null)
            {
                throw new NullReferenceException("AuthResult is Null");
            }

            Message = message;
            AuthResult = authResult;
            _webQuery = webQuery;
        }
        #endregion

        #region Public Functions
        /// <summary>
        /// Login über den Oauth Account
        /// </summary>
        /// <returns></returns>
        public bool Login()
        {
            if (!AuthResult.IsSuccessful)
            {
                Message.Add(ErrorMessages.OauthLoginFailed, MessageTypes.error);
                return false;
            }

            try
            {
                //Prüfen ob der User bereits existiert oder ob dieser neu angelegt werden muss. Dabei wird als erstes nach dem Provider und der passenden UserId gesucht.
                Member member = _webQuery.OAuthMembershipQueries.GetOAuthUser(AuthResult.ProviderUserId, AuthResult.Provider);
                if (member == null)
                {
                    //Wenn der User noch nicht existiert, evtl. prüfen ob bereits ein User mit der passenden Mailadresse angemeldet ist.
                    string userMail = AuthResult.UserName; //Bei Facebook steht hier zumindest die Mailadresse
                    //Prüfen ob darin wirklich eine Mailadresse enthalten ist.
                    if (CheckIfMailAddressIsValid(userMail))
                    {
                        //Den User ermitteln
                        member = _webQuery.MembershipQueries.GetMemberByUsername(userMail);
                    }

                    //Der User existiert noch nicht und muss neu angelegt werden.
                    if (member == null)
                    {
                        //Anlegen des Users
                        member = CreateMember();
                        if (member == null)
                        {
                            return false;
                        }
                    }
                    else
                    {
                        //Wenn ein User gefunden wird und hier bereits ein AuthProvider eingetragen ist, auf eine andere Mailadresse
                        //dann den User darauf hinweisen, das dieser bereits mit einem anderen Provider bei uns registriert ist und bitte diesen verwenden soll
                        if (member.MemberOAuth != null && (!string.IsNullOrEmpty(member.MemberOAuth.Provider) || !string.IsNullOrEmpty(member.MemberOAuth.ProviderUserId)))
                        {
                            //Nur eine Meldung angeben, das der User bereits mit einem Anderen Providerkonto bei uns registriert ist.
                            Message.Add(string.Format(DefaultMessages.AllreadyRegisteredWithAlternateProvider, member.MemberOAuth.Provider), MessageTypes.message);
                            return false;
                        }
                        else
                        {
                            //Den ermittelten User mit dem Providerkonto verbinden, mit dem er sich eingeloggt hat.
                            if (!ConnectToProviderAccount(member))
                            {
                                return false;
                            }
                        }
                    }
                }

                //Ab hier gibt es auf jeden Fall einen User, der angelegt wurde, ....
                FormsAuthentication.SetAuthCookie(member.Id.ToString(), true);
                return true;
            }
            catch (Exception exception)
            {
                Logger.Error(string.Format("Authresult: {0}, {1}, {2}", AuthResult.Provider, AuthResult.ProviderUserId, AuthResult.UserName));
                Logger.Error("Fehler:", exception);
                return false;
            }
        }

        /// <summary>
        /// Verbinden des aktuellen UserAccounts mit einem OAuth Account
        /// </summary>
        public bool JoinProviderAccount()
        {
            if (!AuthResult.IsSuccessful)
            {
                Message.Add(ErrorMessages.OauthLoginFailed, MessageTypes.error);
                return false;
            }

            //Bevor ein User seinen Account mit einem bestimmten Provider berbinden kann, muss erst geporüft werden
            //ob der User nicht schon mit dem Provider auf einem anderen Account verbunden ist.
            Member oAuthMember = _webQuery.OAuthMembershipQueries.GetOAuthUser(AuthResult.ProviderUserId, AuthResult.Provider);
            Member currentUser = _webQuery.MembershipQueries.GetMemberById(long.Parse(HttpContext.Current.User.Identity.Name));
            if (oAuthMember == null)
            {
                //Es konnte kein alternativer Account gefunden werden, damit kann der aktuelle Account mit dem provider verbunden werden
                return ConnectToProviderAccount(currentUser);
            }

            //Wenn der eigene Account bereits damit verbunden ist eine entsprechende Nachricht ausgeben.
            if (currentUser.Id == oAuthMember.Id)
            {
                Message.Add(DefaultMessages.AccountAllreadyConnectedToProvider, MessageTypes.message);
                return true;
            }
            else
            {
                //Der SocialMesiaAccount ist bereits mit einem anderen Account bei myWorktime verbunden und kann daher nicht verbunden werden.
                Message.Add(ErrorMessages.ProviderAllreaddyBinded, MessageTypes.message);
                return false;
            }
        }

        #endregion

        #region Private Functions
        /// <summary>
        /// Verbindet den übergebnen User mit dem übergebenen Provideraccount
        /// </summary>
        /// <param name="member">Der User der mit dem Socialmediaccount verbunden werden soll</param>
        private bool ConnectToProviderAccount(Member member)
        {
            if (member.MemberOAuth == null)
            {
                member.MemberOAuth = new MemberOAuth();
            }

            member.MemberOAuth.Provider = AuthResult.Provider;
            member.MemberOAuth.ProviderUserId = AuthResult.ProviderUserId;
            member.MemberOAuth.ProviderUsername = AuthResult.UserName;
            member.MemberOAuth.Member = member;
            member.MemberOAuth.MemberId = member.Id;
            if (_webQuery.Save())
            {
                Message.Add(string.Format(DefaultMessages.OAuthRegisterSuccessfull, AuthResult.Provider), MessageTypes.ok);
                return true;
            }
            else
            {
                Message.Add(string.Format(ErrorMessages.OAuthRegisterError, AuthResult.Provider), MessageTypes.error);
                return false;
            }
        }

        /// <summary>
        /// Anlegen eines neuen Members
        /// </summary>
        /// <returns></returns>
        private Member CreateMember()
        {
            Member member = new Member();
            member.InializeCurrentMember();
            member.MemberOAuth.Provider = AuthResult.Provider;
            member.MemberOAuth.ProviderUserId = AuthResult.ProviderUserId;
            member.MemberOAuth.ProviderUsername = AuthResult.UserName; //Evtl. die Mailadresse

            if (_webQuery.MembershipQueries.AddMember(member) && _webQuery.Save())
            {
                Message.Add(DefaultMessages.OAuthCreateMemberSuccess, MessageTypes.ok);
                //Der User benötigt nat. auch noch die "Richtigen" Rechte.
                _webQuery.RightQueries.AddDefaultVocabelRights(member.Id);
                return member;
            }
            else
            {
                Message.Add(ErrorMessages.OAuthAccountCreationError, MessageTypes.error);
                return null;
            }
        }

        /// <summary>
        /// Prüft ob es sich um eine Valide Mailadresse handelt die übergeben wurde.
        /// </summary>
        /// <param name="mailAddress">Die Mailadresse die überprüft werden soll</param>
        private bool CheckIfMailAddressIsValid(string mailAddress)
        {
            try
            {
                MailAddress address = new MailAddress(mailAddress);
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }
        #endregion
    }
}
